home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.1 (Developer) [x86]
/
NeXT Step 3.1 Intel dev.cdr.dmg
/
NextDeveloper
/
Examples
/
AppKit
/
Draw
/
Group.m
< prev
next >
Wrap
Text File
|
1993-01-06
|
15KB
|
582 lines
#import "draw.h"
/* Optimally viewed in a wide window. Make your window big enough so that this comment fits entirely on one line w/o wrapping. */
#define GROUP_CACHE_THRESHOLD 4
@implementation Group : Graphic
/*
* This Graphic is used to create heirarchical groups of other Graphics.
* It simply keeps a list of all the Graphics in the group and resizes
* and translates them as the Group object itself is resized and moved.
* It also passes messages sent to the Group onto its members.
*
* For efficiency, we cache the group whenever it passes the caching
* threshold. Thus, grouping becomes a tool to let the user have some
* control over the memory/speed tradeoff (which can be different
* depending on the kind of drawing the user is making).
*/
/* Factory method */
+ initialize
/*
* This bumps the class version so that we can compatibly read
* old Graphic objects out of an archive.
*/
{
[Group setVersion:3];
return self;
}
/* Initialization */
- initList:(List *)list
/*
* Creates a new grouping with list containing the list of Graphics
* in the group. Groups of Groups is perfectly allowable. We have
* to keep track of the largest linewidth in the group as well as
* whether any of the elements of the group have arrows since both
* of those attributes affect the extended bounds of the Group.
* We set any objects which might be cacheing (notably subgroups of
* this group) to be not cacheable since it is no use for them to
* cache themselves when we are caching them as well. We also have
* to check to see if there are any TextGraphic's in the group
* because we can't cache ourselves if there are (unfortunately).
*/
{
int i;
NXRect r;
Graphic *graphic;
[super init];
gFlags.mightBeLinked = YES;
i = [list count];
graphic = [list objectAt:--i];
[graphic getBounds:&bounds];
gFlags.arrow = [graphic lineArrow];
linewidth = [graphic lineWidth];
bounds.size.width = MAX(1.0, bounds.size.width);
bounds.size.height = MAX(1.0, bounds.size.height);
while (i) {
graphic = [list objectAt:--i];
[graphic getBounds:&r];
[graphic setCacheable:NO];
r.size.width = MAX(1.0, r.size.width);
r.size.height = MAX(1.0, r.size.height);
NXUnionRect(&r, &bounds);
if (!gFlags.arrow && [graphic lineArrow]) gFlags.arrow = [graphic lineArrow];
if ([graphic lineWidth] > linewidth) linewidth = [graphic lineWidth];
if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) hasTextGraphic = YES;
}
components = list;
lastRect = bounds;
return self;
}
- free
{
[components freeObjects];
[components free];
[cache free];
return [super free];
}
/* Public methods */
- transferSubGraphicsTo:(List *)list at:(int)position
/*
* Called by Ungroup. This just unloads the components into the
* passed list, modifying the bounds of each of the Graphics
* accordingly (remember that when a Graphic joins a Group, its
* bounds are still kept in GraphicView coordinates (not
* Group-relative coordinates), but they may be non-integral,
* we can't allow non-integral bounds outside a group because
* it conflicts with the compositing rules (and we use
* compositing to move graphics around).
*/
{
int i, count;
Graphic *graphic;
NXRect gbounds;
BOOL zeroWidth, zeroHeight;
count = [components count];
for (i = (count - 1); i >= 0; i--) {
graphic = [components objectAt:i];
[graphic getBounds:&gbounds];
if (!gbounds.size.width) {
zeroWidth = YES;
gbounds.size.width = 1.0;
} else zeroWidth = NO;
if (!gbounds.size.height) {
zeroHeight = YES;
gbounds.size.height = 1.0;
} else zeroHeight = NO;
NXIntegralRect(&gbounds);
if (zeroWidth) gbounds.size.width = 0.0;
if (zeroHeight) gbounds.size.height = 0.0;
[graphic setBounds:&gbounds];
[graphic setCacheable:YES];
[list insertObject:graphic at:position];
}
return self;
}
- (List *)subGraphics
{
return components;
}
/* Group must override all the setting routines to forward to components */
- makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument
{
[components makeObjectsPerform:aSelector with:(id)anArgument];
[cache free];
cache = nil;
return self;
}
- changeFont:sender
{
return [self makeGraphicsPerform:@selector(changeFont:) with:sender];
}
- (Font *)font
{
int i;
Font *gfont, *font = nil;
i = [components count];
while (i--) {
gfont = [[components objectAt:i] font];
if (gfont) {
if (font && font != gfont) {
font = nil;
break;
} else {
font = gfont;
}
}
}
return font;
}
- setLineWidth:(const float *)value
{
return [self makeGraphicsPerform:@selector(setLineWidth:) with:value];
}
- setGray:(const float *)value
{
return [self makeGraphicsPerform:@selector(setGray:) with:value];
}
- setFillColor:(NXColor *)aColor
{
return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor];
}
- setFill:(int)mode
{
return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode];
}
- setLineColor:(NXColor *)aColor
{
return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor];
}
- setLineCap:(int)value
{
return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value];
}
- setLineArrow:(int)value
{
return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value];
}
- setLineJoin:(int)value
{
return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value];
}
/* Link methods */
/*
* Called after unarchiving and after a linkManager has been created for
* the document this Graphic is in. Graphic's implementation of this just
* adds the link to the linkManager.
*/
- reviveLink:(NXDataLinkManager *)linkManager
{
[components makeObjectsPerform:@selector(reviveLink:) with:linkManager];
return self;
}
/*
* This returns self if there is more than one linked Graphic in the Group.
* If aLink is not nil, returns the Graphic which is linked by that link.
* If aLink is nil, then it returns the one and only linked Graphic in the
* group or nil otherwise. Used when updating the link panel and when
* redrawing link outlines.
*/
- (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
{
int i, linkCount = 0;
Graphic *graphic = nil;
for (i = [components count]-1; i >= 0; i--) {
if (graphic = [[components objectAt:i] graphicLinkedBy:aLink]) {
if ([graphic isKindOf:[Group class]]) return graphic;
linkCount++;
}
}
return (linkCount <= 1) ? graphic : self;
}
/*
* When you copy/paste a Graphic, its identifier must be reset to something
* different since you don't want the pasted one to have the same identifier
* as the copied one! See gvPasteboard.m.
*/
- resetIdentifier
{
[components makeObjectsPerform:@selector(resetIdentifier)];
return self;
}
/*
* Used when creating an NXSelection representing all the Graphics
* in a selection. Has to recurse through Groups because you still
* want the NXSelection to be valid even if the Graphics are ungrouped
* in the interim between the time the selection is determined to the
* time the links stuff asks questions about the selection later.
*/
- writeIdentifierTo:(char *)buffer
{
int i = [components count];
char *s = buffer;
if (i) {
[[components objectAt:--i] writeIdentifierTo:s];
s += strlen(s);
while (i--) {
*s++ = ' ';
[[components objectAt:i] writeIdentifierTo:s];
s += strlen(s);
}
}
return self;
}
/*
* This is used by the links stuff to allocate a buffer big enough to
* put all the identifiers for all the graphics in this Group into.
*/
- (int)graphicCount
{
int count = 0, i = [components count];
while (i--) count += [[components objectAt:i] graphicCount];
return count;
}
/*
* See the method findGraphicInSelection: in gvLinks.m to see how this
* method is used (it basically just lets you get back to a Graphic
* from its identifier whether its in a Group or not).
*/
- (Graphic *)graphicIdentifiedBy:(int)anIdentifier
{
int i = [components count];
while (i--) {
Graphic *graphic = [components objectAt:i];
if (graphic = [graphic graphicIdentifiedBy:anIdentifier]) return graphic;
}
return nil;
}
/*
* We pass this method onto all the things inside the group since
* there might be linked things inside the group.
*/
- readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
{
int i = [components count];
while (i--) {
Graphic *graphic = [components objectAt:i];
if ([graphic mightBeLinked]) [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewIdentifier];
}
return nil;
}
/* Form Entry methods. See TextGraphic.m for details. */
- (BOOL)hasFormEntries
{
int i = [components count];
while (i--) if ([[components objectAt:i] hasFormEntries]) return YES;
return NO;
}
- (BOOL)writeFormEntryToStream:(NXStream *)stream
{
BOOL retval = NO;
int i = [components count];
while (i--) if ([[components objectAt:i] writeFormEntryToStream:stream]) retval = YES;
return retval;
}
/* Notification methods */
- wasRemovedFrom:(GraphicView *)sender
{
[components makeObjectsPerform:@selector(wasRemovedFrom:) with:sender];
[cache free];
cache = nil;
return self;
}
- wasAddedTo:(GraphicView *)sender
{
[components makeObjectsPerform:@selector(wasAddedTo:) with:sender];
return self;
}
/* Color drag-and-drop support. */
- (Graphic *)colorAcceptorAt:(const NXPoint *)point
{
int i, count;
Graphic *graphic;
count = [components count];
for (i = 0; i < count; i++) {
if (graphic = [[components objectAt:i] colorAcceptorAt:point]) return graphic;
}
return nil;
}
/* We can't cache ourselves if we have a TextGraphic in the Group. */
- (BOOL)hasTextGraphic
{
return hasTextGraphic;
}
- setCacheable:(BOOL)flag
/*
* Sets whether we do caching of this Group or not.
*/
{
dontCache = flag ? NO : YES;
if (dontCache) {
[cache free];
cache = nil;
}
return self;
}
- (BOOL)isCacheable
{
return !hasTextGraphic && !dontCache;
}
- draw
/*
* Individually scales and translates each Graphic in the group and draws
* them. This is done this way so that ungrouping is trivial. Note that
* if we are caching, we need to take the extra step of translating
* everything to the origin, drawing them in the cache, then translating
* them back.
*/
{
int i;
Graphic *g;
NXRect eb, b;
float sx = 1.0, sy = 1.0, tx, ty;
BOOL changed, changedSize, caching = NO;
if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self;
changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height;
changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y;
if ((changedSize || !cache) && NXDrawingStatus == NX_DRAWING) {
[cache free];
cache = nil;
if (DrawStatus != Resizing && [self isCacheable] && [components count] > GROUP_CACHE_THRESHOLD) {
caching = YES;
[self getExtendedBounds:&eb];
cache = [[NXImage allocFromZone:[self zone]] initSize:&eb.size];
[cache lockFocus];
[[[NXApp focusView] window] reenableDisplay]; /* workaround for AppKit bug? */
PStranslate(- eb.origin.x, - eb.origin.y);
PSsetalpha(0.0);
PSsetgray(NX_WHITE);
NXRectFill(&eb);
PSsetalpha(1.0);
}
}
if (changedSize) {
sx = bounds.size.width / lastRect.size.width;
sy = bounds.size.height / lastRect.size.height;
}
i = [components count];
while (i) {
g = [components objectAt:--i];
if (changed) {
[g getBounds:&b];
tx = (bounds.origin.x + ((b.origin.x - lastRect.origin.x) / lastRect.size.width * bounds.size.width)) - b.origin.x;
ty = (bounds.origin.y + ((b.origin.y - lastRect.origin.y) / lastRect.size.height * bounds.size.height)) - b.origin.y;
b.origin.x = b.origin.x + tx;
b.origin.y = b.origin.y + ty;
b.size.width = b.size.width * sx;
b.size.height = b.size.height * sy;
[g setBounds:&b];
}
if (NXDrawingStatus != NX_DRAWING || !cache || caching) {
[g setGraphicsState]; /* does a gsave ... */
[g draw];
PSgrestore(); /* ... so we need this grestore */
}
}
if (cache && NXDrawingStatus == NX_DRAWING) {
if (caching) {
[cache unlockFocus];
} else {
[self getExtendedBounds:&eb];
}
[cache composite:NX_SOVER toPoint:&eb.origin];
}
lastRect = bounds;
return self;
}
- (BOOL)hit:(const NXPoint *)point
/*
* Gets a hit if any of the items in the group gets a hit.
*/
{
int i;
NXPoint p;
float px, py;
Graphic *graphic;
if ([super hit:point]) {
if (components) {
p = *point;
px = (p.x - bounds.origin.x) / bounds.size.width;
p.x = px * lastRect.size.width + lastRect.origin.x;
py = (p.y - bounds.origin.y) / bounds.size.height;
p.y = py * lastRect.size.height + lastRect.origin.y;
i = [components count];
while (i) {
graphic = [components objectAt:--i];
if ([graphic hit:&p]) return YES;
}
} else {
return YES;
}
}
return NO;
}
/* Compatibility methods */
- replaceWithImage
/*
* Since we got rid of Tiff and PSGraphic and replaced them
* with the unified Image graphic, we need to go through our
* list and replace all of them with an Image graphic.
*/
{
int i;
Graphic *graphic, *newGraphic;
for (i = [components count]-1; i >= 0; i--) {
graphic = [components objectAt:i];
newGraphic = [graphic replaceWithImage];
if (graphic != newGraphic) {
if (graphic) {
[components replaceObjectAt:i with:newGraphic];
} else {
[components removeObjectAt:i];
}
}
}
return self;
}
/* Archiving methods */
- write:(NXTypedStream *)stream
/*
* Just writes out the components.
*/
{
[super write:stream];
NXWriteTypes(stream, "@", &components);
NXWriteType(stream, "c", &dontCache);
NXWriteRect(stream, &lastRect);
NXWriteType(stream, "c", &hasTextGraphic);
return self;
}
static BOOL checkForTextGraphic(List *list)
{
int i;
Graphic *graphic;
for (i = [list count]-1; i >= 0; i--) {
graphic = [list objectAt:i];
if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) return YES;
}
return NO;
}
- read:(NXTypedStream *)stream
{
[super read:stream];
NXReadTypes(stream, "@", &components);
lastRect = bounds;
if (NXTypedStreamClassVersion(stream, "Group") > 1) {
NXReadType(stream, "c", &dontCache);
NXReadRect(stream, &lastRect);
}
if (NXTypedStreamClassVersion(stream, "Group") > 2) {
NXReadType(stream, "c", &hasTextGraphic);
} else {
hasTextGraphic = checkForTextGraphic(components);
}
return self;
}
@end